package org.zn.note.jdk.java8.Java8InAction;

import org.zn.note.jdk.java8.Java8InAction.bean.Apple;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * 第5章 使用流
 * <p>
 * 5.1 筛选和切片
 * 5.1.1 谓词筛选       {@link Stream#filter(Predicate)} filter接收一个谓词(predicate, 返回值为boolean)，筛选出true的数据。
 * 5.1.2 筛选各异元素   {@link Stream#distinct()} 去重，根据 hashCode + equals
 * 5.1.3 截短           {@link Stream#limit(long)}
 * 5.1.4 跳过元素       {@link Stream#skip(long)} 扔掉前N个元素，组成新的流，和上面的limit互补。
 * <p>
 * 5.2 映射
 * 5.2.1 从数据对象中选择要素，类似SQL中选择列
 * {@link Stream#map(Function)} 对流中每个元素应用函数，函数的执行结果组成新的流
 * 5.2.2 流的扁平化
 * {@link Arrays#stream(Object[])} 数组转成对象流
 * {@link Stream#flatMap(Function)} 两种理解：
 * 1、把流中的每一个值都生成一个流，然后把这些流拼上 （官方）
 * 2、就是把原来数组流里的每个元素拿出来，拼成一个流（我的理解） Stream{[1,2,3],[4,5,6]...} --> Stream{1,2,3,4,5,6,...}
 * 例子 {@link Ch5#testArraysStream1()}
 * <p>
 * 5.3 查找和匹配
 * 5.3.1 {@link Stream#anyMatch(Predicate)} 流中是否有要素满足谓词，只要有就返回true。因为返回boolean，所以是终端操作。
 * 5.3.2 {@link Stream#allMatch(Predicate)}、{@link Stream#noneMatch(Predicate)}
 * 上面都是“短路”的，就是满足条件就返回了，不用看全部的流，适用于无限流。
 * 类似的短路还有 findFirst、findAny、limit
 * 5.3.3 查找元素 {@link Stream#findAny()}
 * 5.3.4 查找第一个元素 {@link Stream#findFirst()}
 * 在并行是，findAny找到一个就返回了，而findFirst只找到最近的一个。
 * 两者都返回 {@link Optional}，防null的
 * <p>
 * 5.4 归约
 * 关于终端操作，有返回boolean的（allMatch等）、void的（forEach的）、Optional的（findAny等）、还有collect
 * 将流中的所有元素经过反复结合，得到一个值，称为归约操作，函数式编程术语---折叠(fold)。
 * <p>
 * 5.6 数值流
 * 1、对象流和数值流的转换 {@link Ch5#testMapToInt()}
 * <p>
 * 5.7 构造流 {@link Ch5#createStream()}
 * 流对于表达数据处理查询是非常强大的。
 * 6227 0000 1284 0316 796
 */
class Ch5 {

    // 谓词筛选
    private static void testFilter1() {
        List<Apple> apples = Apple.getApples();
        long redAppleNumber = apples.stream().filter(Apple::isRedApple).count();
        System.out.println("Red Apple Number is " + redAppleNumber);
    }

    // 去重
    private static void testDistinct1() {
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 4, 5, 6, 6, 5, 4, 7, 7, 8, 9);
        numbers.stream().distinct().forEach(System.out::println);
    }

    // 流中每个元素执行函数，起到“选择”的效果
    private static void testMap1() {
        List<Apple> apples = Apple.getApples();
        apples.stream()
                .map(Apple::getWeight) // "选择"出苹果的重量，类似 select weight from apples
                .forEach(System.out::println);
    }

    // 需求：一句话里有多少个不重复的字母，比如 “hello world”--> h、e、l、o、w、r、d
    private static void testArraysStream1() {
        String[] words = "hello world".split(" "); // 句子转成字符串数组
        List l = Arrays.stream(words) // 将字符串数组String[] 转成 字符串流 Stream<String>
                .map(word -> word.split("")) // 将字符串流<String> 转成 字符串数组流 Stream<String[]> ，这里的数组都是一个一个的字符了
                .flatMap(Arrays::stream) // 将字符串数组流 扁平化成 字符串流Stream<String>（其实这里已经是字符流了）
                .distinct()
                .collect(Collectors.toList());
        System.out.println(l);
    }

    private static void testReduce1() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        // 普通求和
        int sum1 = 0;
        for (int i : numbers)
            sum1 += i;
        // stream reduce 求和
        // T reduce(T identity, BinaryOperator<T> accumulator);  identity初始值，accumulator把两个数合成一个数
        int sum2 = numbers.stream().reduce(0, (a, b) -> a + b);
        // 1.8 Integer.sum
        int sum3 = numbers.stream().reduce(0, Integer::sum);

        System.out.printf("sum1:%d, sum2:%d, sum3:%d\n", sum1, sum2, sum3);

        // 求最大值、最小值
        Optional<Integer> max1 = numbers.stream().reduce((a, b) -> a > b ? a : b);
        Optional<Integer> min1 = numbers.stream().reduce((a, b) -> a < b ? a : b);
        Optional<Integer> max2 = numbers.stream().reduce(Integer::max);
        Optional<Integer> min2 = numbers.stream().reduce(Integer::min);
        System.out.printf("max1:%d, min1:%d, max2:%d, min2:%d\n", max1.get(), min1.get(), max2.get(), min2.get());

    }

    // 对象流、数值流
    private static void testMapToInt() {
        List<Apple> apples = Apple.getApples();
        // ① 这样写可以，但是有个装箱成本，每次都是把 int 转成 Integer
        int maxWeight = apples.stream().map(Apple::getWeight).reduce(0, Integer::max);
        // ② 这样写没有装箱成本，但是 map()产生Stream<T>，有没有sum方法
//        apples.stream().map(Apple::getWeight).sum();
        // ③ 把原本map()产生的原始数据流(对象流)，转成特殊数据流(数值流)，进而避免在计算过程中的装箱成本
        // 1.8 引入 IntStream、DoubleStream、LongStream 三种数值流，里面直接带一些计算的方法，比如 sum
        IntStream intStream = apples.stream().mapToInt(Apple::getWeight); // 将对象流转成数值流
        Stream objectStream = intStream.boxed(); // 数值流转成对象流
        OptionalInt max = intStream.max(); // 数值流自带计算方法，同时也避免装箱
        int maxWeight2 = max.orElse(100); // 如果没有数据，给个默认值
    }

    // 数值范围：打印100以内的质数
    private static void testRange() {
        // IntStream、LongStream有两个方法生成数值范围，range(不包含结束值)，rangeClosed(包含结束值)
        IntStream intStream = IntStream.rangeClosed(1, 100); // 生成 [1, 100] 的整数流
//        intStream.filter(MathUtil::isPrimeNumber).forEach(System.out::println);
    }

    // 5.7 创建流
    private static void createStream() {
        // 5.7.1 字符串流 Stream.of()
        Stream<String> stringStream = Stream.of("my", "name", "is", "zhang");
        stringStream.map(String::toUpperCase) // map 将一个流<T>通过遍历-调用指定方法-返回结果，将结果生成新流<R>
                .forEach(s -> System.out.printf("%s ", s));
        System.out.println();
        // 空流
        Stream emptyStream = Stream.empty();

        // 5.7.2 通过数组创建流 Arrays.stream()
        // ① 基础数据类型创建数值流 int[] --> IntStream  long[] --> LongStream  double[] --> DoubleStream
        // ② 对象数组创建对象流     T[] --> Stream<T>
        int[] numbers = {1, 2, 3, 4, 5, 6};
        int numbersSum = Arrays.stream(numbers) // IntStream
                .sum(); // IntStream.sum

        // 5.7.3 文件生成流
        String filePath = "d://a.txt";
        try (
                // java.nio.file.Files 工具类 .lines()  从文件readLine生成 Stream<String>
                Stream<String> lines = Files.lines(Paths.get(filePath), Charset.defaultCharset())
        ) {
            long uniqueWords = lines
                    .flatMap(line -> Arrays.stream(line.split(" "))) // 空格分词，流合并，生成单词流
                    .distinct() // 去重
                    .count(); // 计数
            System.out.printf("uniqueWords is %d\n", uniqueWords); // 文件里有多少不重复的单词
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 5.7.4 有函数生成流：创建无限流
        // Stream.iterate\generate 创建无限流，一般配合limit使用，用于计算数值
        Stream.iterate(0, n -> n + 2) // 迭代，从0开始，创建正偶数无限流
                .limit(10) // 前10个正偶数
                .forEach(a -> System.out.printf("%d ", a));
        System.out.println();
        // 斐波那契数列：前2个数相加等于第3个数，以此类推，0 1 1 2 3 5 8 13 21 ...
        Stream.iterate(new int[]{0, 1}, i -> new int[]{i[0] + i[1], i[0] + i[1] + i[1]})
                .limit(20)
                .forEach(i -> System.out.printf("%d %d ", i[0], i[1]));
        System.out.println();
        // Stream.generate 传入一个方法，能生成新值就行
        Stream.generate(Math::random).limit(5).forEach(a -> System.out.printf("%f ", a)); // 0~1之间随机数
        System.out.println();
    }

    public static void main(String[] args) {
        createStream();
    }
}
